feat: deserialize responses into typed Pydantic models - #20
Merged
Conversation
…6266) Both HttpClient and SyncHttpClient.request() now take an optional response_type and validate the JSON body through a cached pydantic TypeAdapter. TypeVar + overloads make the type flow to call sites: response_type=Model infers Model, list[Model] infers the list, and omitting it keeps the raw-dict behavior auth.py and channels rely on. A bodyless 204 on an operation that promises a typed body raises ValidationError instead of silently returning None. Ships py.typed so type checkers actually read the package's annotations (mypy previously treated the entire SDK as untyped), and the SDK + contract tests regenerated with the generator that emits response_type= per call (ArchAstro/archastro-openapi companion PR). Contract tests now assert concrete Pydantic classes instead of dict access. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Companion generator PR: ArchAstro/archastro-openapi#28. Merge order: this PR first; publish the new generator version only after both land (regenerate workflow installs @latest). |
|
✅ Clean PR, @rob-archastro! No blocking findings on
Reply |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Fixes firstlanding#6266: every SDK method was annotated as returning a Pydantic model but returned a raw dict at runtime — no deserialization step existed.
HttpClient.request()andSyncHttpClient.request()now accept an optionalresponse_typeand validate the JSON body through a cachedpydantic.TypeAdapter. Generics (TypeVar+@overload) make the type flow to call sites; hand-written callers (auth.py, channels) that omitresponse_typekeep raw-dict behavior. A bodyless 204 on an operation promising a typed body raisesValidationErrorat the call. Shipspy.typed(verified present in the wheel) — without it mypy treated the whole package as untyped. Includes the regenerated SDK + contract tests from the companion generator PR; contract tests now assert concrete Pydantic classes.sequenceDiagram participant C as Caller participant R as Generated resource method participant H as HttpClient.request participant X as httpx C->>R: installations.activate(id) R->>H: request(path, response_type=Installation) H->>X: HTTP request X-->>H: response alt status 204 and response_type set H-->>C: raises ValidationError as server omitted promised body else response_type set H->>H: TypeAdapter(Installation).validate_python(json) H-->>C: Installation instance else no response_type H-->>C: raw parsed JSON endclassDiagram class HttpClient { request(path, response_type) T request_raw(path) dict } class SyncHttpClient { request(path, response_type) T request_raw(path) dict } class TypeAdapterCache { _type_adapter(tp) TypeAdapter } HttpClient ..> TypeAdapterCache : validates via SyncHttpClient ..> TypeAdapterCache : validates viaScope: backend-only (Python SDK runtime + generated code).
Risk: medium — runtime breaking change for SDK users doing dict access on responses (
resp["id"]→resp.id); the SDK is at 0.1.1 with few consumers. Release should be a minor bump (0.2.0) with a changelog note. The validation surfaced and the companion PR fixed a latent typegen bug (object-field annotation shadowing) that made everyAttachment.objectvalue fail validation.User impact: SDK responses become real Pydantic models — IDE autocomplete, validation, datetime coercion; mypy/pyright now type-check actual calls (verified with both).
Testing: red-first runtime tests (model /
list[Model]/ raw-dict / 204 both paths /ValidationError, both transports); 38 http_client tests total incl. new coverage of request assembly, token precedence, path-prefix, error parsing; 1,720 regenerated contract tests against Prism; harness + phx_channel suites; ruff clean. Full local suite: 1,799 passed.Follow-ups: none in this repo. PyPI release timing is a separate decision.
🤖 Generated with Claude Code